package ita.class1.group2.supermarket.common.dao.impl;
/**
 * @ Author     ：LILA3
 * @ Date       ：Created in 4:42 PM 7/13/2018
 */

import ita.class1.group2.supermarket.common.annotation.Autowired;
import ita.class1.group2.supermarket.common.dao.BaseDao;
import ita.class1.group2.supermarket.common.dao.annotation.ColumnIgnore;
import ita.class1.group2.supermarket.common.dao.annotation.ColumnName;
import ita.class1.group2.supermarket.common.dao.annotation.Table;
import ita.class1.group2.supermarket.common.util.ConnectionManager;
import org.apache.log4j.Logger;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.sql.Date;
import java.util.*;

public class BaseDaoImpl<T, PK extends Serializable> implements BaseDao<T, PK> {
    private static final Logger LOGGER = Logger.getLogger(BaseDaoImpl.class);

    @Autowired
    private ConnectionManager connectionManager;

    private Class<T> entityClass = null;

    public BaseDaoImpl() {
        Type genType = this.getClass().getGenericSuperclass();
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        entityClass = (Class) params[0];
    }

    @Override
    public T save(T entity) {
        String sql = "insert into " + getTableAnnotation(entityClass).toLowerCase() + "(";

        List<Method> list = getMethodWithGet(entityClass);
        for (Method getMethod : list) {
            sql += getColumnName(getMethod) + ",";
        }
        sql = sql.substring(0, sql.lastIndexOf(",")) + ") values(";

        for (Method getMethod : list) {
            sql += "?,";
        }
        sql = sql.substring(0, sql.lastIndexOf(",")) + ")";

        LOGGER.info(sql);

        try {

            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
            setParams(entity, statement, list);
            if (statement.executeUpdate() > 0) {
                return entity;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public boolean update(T entity) {
        String sql = "update " + getTableAnnotation(entityClass).toLowerCase() + " set ";

        List<Method> list = getMethodWithGet(entityClass);
        Method idMethod = null;
        for (Method getMethod : list) {
            if (getMethod.getName().substring(3).toLowerCase().equals("id")) {
                idMethod = getMethod;
                continue;
            }
            sql += getColumnName(getMethod) + " = ?,";
        }

        try {
            String id = (String) idMethod.invoke(entity);
            sql = sql.substring(0, sql.lastIndexOf(",")) + " where id = '" + id + "'";
            list.remove(idMethod);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        LOGGER.info(sql);
        try {
            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
            setParams(entity, statement, list);
            if (statement.executeUpdate() > 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean updateByCon(T entity, Map map) {
        String sql = "update " + getTableAnnotation(entityClass).toLowerCase() + " set ";

        List<Method> list = getMethodWithGet(entityClass);
        Method idMethod = null;
        for (Method getMethod : list) {
            if (getMethod.getName().substring(3).toLowerCase().equals("id")) {
                idMethod = getMethod;
                continue;
            }
            sql += getColumnName(getMethod) + " = ?,";
        }

        try {
            String id = (String) idMethod.invoke(entity);
            sql = sql.substring(0, sql.lastIndexOf(",")) + " where id = '" + id + "'";
            list.remove(0);
            if (map != null) {
                Set keys = map.keySet();
                for (Object key : keys) {
                    sql += " and " + key.toString() + " = '" + map.get(key).toString() + "'";
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        LOGGER.info(sql);
        try {
            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
            setParams(entity, statement, list);
            if (statement.executeUpdate() > 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    @Override
    public boolean delete(PK pk) {
        String sql = "delete from " + getTableAnnotation(entityClass).toLowerCase();

        List<Method> list = getMethodWithGet(entityClass);
        try {
            sql = sql + " where id = '" + pk + "'";
            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
            if (statement.executeUpdate() > 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public List<T> findByCondition(Map map) {
//        String sql = "select * from " + getTableAnnotation(entityClass).toLowerCase();
//
//        if (map != null) {
//            sql += " where ";
//            Set keys = map.keySet();
//            for (Object key : keys) {
//                sql += key.toString() + " = '" + map.get(key).toString() + "' and ";
//            }
//            sql = sql.substring(0, sql.indexOf("and"));
//        }
//
//        LOGGER.info(sql);
//        try {
//            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
//            ResultSet rs = statement.executeQuery();
//
//            int i = 0;
//            List<Method> list = getMethodWithSet(entityClass);
//            List<T> entityLsit = new ArrayList<T>();
//            while (rs.next()) {
//                T entity = entityClass.newInstance();
//                for (Method method : list) {
//
//                    if (method.getParameterTypes()[0].getSimpleName().indexOf("String") != -1) {
//                        method.invoke(entity, rs.getString(getColumnName(method)));
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Date") != -1) {
//                        method.invoke(entity, rs.getDate(getColumnName(method)));
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Double") != -1) {
//                        method.invoke(entity, rs.getDouble(getColumnName(method)));
//                    } else if (method.getReturnType().getSimpleName().indexOf("Timestamp") != -1) {
//                        method.invoke(entity, rs.getTimestamp(getColumnName(method)));
//                    } else {
//                        method.invoke(entity, rs.getInt(getColumnName(method)));
//                    }
//                }
//                entityLsit.add(entity);
//            }
//            rs.close();
//            statement.close();
//            return entityLsit;
//        } catch (Exception e) {
//            e.printStackTrace();
//            return null;
//        }
        return superFind(map, null, null, null, null);
    }

    @Override
    public List<T> findByConWithSort(Map map, boolean isAsc, String sortkey, Integer num) {
//        if (sortkey == null) {
//            return findAll();
//        }
//        String sortSql = "select * from ( %s ) t ";
//        if (num != null) {
//            sortSql += " where rownum <= " + num;
//        }
//
//
//        String sql = "select * from " + getTableAnnotation(entityClass).toLowerCase() + " where ";
//
//        if (map != null) {
//
//            Set keys = map.keySet();
//            for (Object key : keys) {
//                sql += key.toString() + " = '" + map.get(key).toString() + "' and ";
//            }
//        }
//
//        sql += sortkey + " is not null";
//        sql += " order by " + sortkey;
//        if (isAsc) {
//            sql += " asc";
//        } else {
//            sql += " desc";
//        }
//        sortSql = String.format(sortSql, sql);
//        LOGGER.info(sortSql);
//        try {
//            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sortSql);
//            ResultSet rs = statement.executeQuery();
//
//            int i = 0;
//            List<Method> list = getMethodWithSet(entityClass);
//            List<T> entityLsit = new ArrayList<T>();
//            while (rs.next()) {
//                T entity = entityClass.newInstance();
//                for (Method method : list) {
//
//                    if (method.getParameterTypes()[0].getSimpleName().indexOf("String") != -1) {
//                        method.invoke(entity, rs.getString(getColumnName(method)));
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Date") != -1) {
//                        method.invoke(entity, rs.getDate(getColumnName(method)));
//
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Double") != -1) {
//                        method.invoke(entity, rs.getDouble(getColumnName(method)));
//                    } else if (method.getReturnType().getSimpleName().indexOf("Timestamp") != -1) {
//                        method.invoke(entity, rs.getTimestamp(getColumnName(method)));
//                    } else {
//                        method.invoke(entity, rs.getInt(getColumnName(method)));
//                    }
//                }
//                entityLsit.add(entity);
//            }
//            rs.close();
//            statement.close();
//            return entityLsit;
//        } catch (Exception e) {
//            e.printStackTrace();
//            return null;
//        }
        return superFind(map, isAsc, sortkey, 1, num);
    }

    @Override
    public T find(PK pk) {
//        String sql = "select * from " + getTableAnnotation(entityClass).toLowerCase() + " where id = '" + pk + "'";
//
//        try {
//            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
//            T entity = entityClass.newInstance();
//
//            List<Method> list = getMethodWithSet(entityClass);
//            ResultSet rs = statement.executeQuery();
//            int i = 0;
//            while (rs.next()) {
//                for (Method method : list) {
//
//                    if (method.getParameterTypes()[0].getSimpleName().indexOf("String") != -1) {
//                        method.invoke(entity, rs.getString(getColumnName(method)));
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Date") != -1) {
//                        method.invoke(entity, rs.getDate(getColumnName(method)));
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Double") != -1) {
//                        method.invoke(entity, rs.getDouble(getColumnName(method)));
//                    } else if (method.getReturnType().getSimpleName().indexOf("Timestamp") != -1) {
//                        method.invoke(entity, rs.getTimestamp(getColumnName(method)));
//                    } else {
//                        method.invoke(entity, rs.getInt(getColumnName(method)));
//                    }
//                }
//            }
//            rs.close();
//            statement.close();
//            return entity;
//        } catch (Exception e) {
//            e.printStackTrace();
//            return null;
//        }
        Map map = new HashMap();
        map.put("id", pk);
        List<T> ts = superFind(map, null, null, 1, null);
        return (ts != null && ts.size() > 0) ? ts.get(0) : null;
    }

    @Override
    public List<T> findAll() {
//        String sql = "select * from " + getTableAnnotation(entityClass).toLowerCase();
//        try {
//            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
//            ResultSet rs = statement.executeQuery();
//
//            int i = 0;
//            List<Method> list = getMethodWithSet(entityClass);
//            List<T> entityLsit = new ArrayList<T>();
//            while (rs.next()) {
//                T entity = entityClass.newInstance();
//                for (Method method : list) {
//
//                    if (method.getParameterTypes()[0].getSimpleName().indexOf("String") != -1) {
//                        method.invoke(entity, rs.getString(getColumnName(method)));
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Date") != -1) {
//                        method.invoke(entity, rs.getDate(getColumnName(method)));
//                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Double") != -1) {
//                        method.invoke(entity, rs.getDouble(getColumnName(method)));
//                    } else if (method.getReturnType().getSimpleName().indexOf("Timestamp") != -1) {
//                        method.invoke(entity, rs.getTimestamp(getColumnName(method)));
//                    } else {
//                        method.invoke(entity, rs.getInt(getColumnName(method)));
//                    }
//                }
//                entityLsit.add(entity);
//            }
//            rs.close();
//            statement.close();
//            return entityLsit;
//        } catch (Exception e) {
//            e.printStackTrace();
//            return null;
//        }
        return superFind(null, null, null, null, null);
    }

    @Override
    public List<T> superFind(Map map, Boolean isAsc, String sortkey, Integer page, Integer rowNum) {

        String sql = "";
        String baseSql = "select * from " + getTableAnnotation(entityClass).toLowerCase();
        if (map != null && map.size() > 0) {
            baseSql += " where ";
            Object[] keys = map.keySet().toArray();
            for (int i = 0; i < keys.length; i++) {
                baseSql += keys[i].toString() + " = '" + map.get(keys[i]).toString() + "'";
                if (i < keys.length - 1) {
                    baseSql += " and ";
                }
            }
        }
        if (sortkey != null && isAsc != null) {
            if (map == null || map.size() == 0) {
                baseSql += " where ";
            } else {
                baseSql += " and ";
            }
            baseSql += sortkey + " is not null ";
            baseSql += " order by " + sortkey;
            if (isAsc) {
                baseSql += " asc ";
            } else {
                baseSql += " desc ";
            }
        }
        if (page != null && rowNum != null) {
            int startRN = (page - 1) * rowNum;
            int endRN = page * rowNum + 1;
            sql = "SELECT * FROM (SELECT A.*,ROWNUM RN FROM(%s) A WHERE ROWNUM < " + endRN + ")WHERE RN > " + startRN;
            sql = String.format(sql, baseSql);
        } else {
            sql = baseSql;
        }
        LOGGER.info(sql);

        try {
            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
            ResultSet rs = statement.executeQuery();
            int i = 0;
            List<Method> list = getMethodWithSet(entityClass);
            List<T> entityLsit = new ArrayList<T>();
            while (rs.next()) {
                T entity = entityClass.newInstance();
                for (Method method : list) {
                    if (method.getParameterTypes()[0].getSimpleName().indexOf("String") != -1) {
                        method.invoke(entity, rs.getString(getColumnName(method)));
                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Date") != -1) {
                        method.invoke(entity, rs.getDate(getColumnName(method)));

                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Double") != -1) {
                        method.invoke(entity, rs.getDouble(getColumnName(method)));
                    } else if (method.getParameterTypes()[0].getSimpleName().indexOf("Long") != -1) {
                        method.invoke(entity, rs.getLong(getColumnName(method)));
                    } else {
                        method.invoke(entity, rs.getInt(getColumnName(method)));
                    }
                }
                entityLsit.add(entity);
            }
            rs.close();
            statement.close();
            return entityLsit;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public int findCount(Map map) {
        int result = 0;
        String sql = "select COUNT(*) from " + getTableAnnotation(entityClass).toLowerCase();
        if (map != null && map.size() > 0) {
            sql += " where ";
            Object[] keys = map.keySet().toArray();
            for (int i = 0; i < keys.length; i++) {
                sql += keys[i].toString() + " = '" + map.get(keys[i]).toString() + "'";
                if (i < keys.length - 1) {
                    sql += " and ";
                }
            }
        }
        LOGGER.info(sql);
        try {
            PreparedStatement statement = connectionManager.getConnection().prepareStatement(sql);
            ResultSet rs = statement.executeQuery();

            if (rs.next()) {
                result = rs.getInt(1);
            }
            rs.close();
            statement.close();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    private List<Method> getMethodWithGet(Class<T> entityClass) {

        List<Method> getMethods = new ArrayList<Method>();
        Method[] methods = entityClass.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getAnnotation(ColumnIgnore.class) != null)
                continue;
            if (method.getName().startsWith("get")) {
                getMethods.add(method);
            }
        }

        return getMethods;
    }

    private List<Method> getMethodWithSet(Class<T> entityClass) {

        List<Method> getMethods = new ArrayList<Method>();
        Method[] methods = entityClass.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getAnnotation(ColumnIgnore.class) != null)
                continue;
            if (method.getName().startsWith("set")) {
                getMethods.add(method);
            }
        }

        return getMethods;
    }

    private String getTableAnnotation(Class<T> entityClass) {
        Table table = entityClass.getAnnotation(Table.class);
        return table.value();
    }

    private String getColumnName(Method method) {
        ColumnName columnName = method.getAnnotation(ColumnName.class);
        if (columnName != null) {
            return columnName.value().toLowerCase();
        } else {
            String methodName = method.getName();
            return methodName.substring(3).toLowerCase();
        }
    }

    private void setParams(T entity, PreparedStatement statement, List<Method> methods) throws Exception {
        int i = 1;
        for (Method getMethod : methods) {
            if (getMethod.getReturnType().getSimpleName().indexOf("String") != -1) {
                statement.setString(i++, (String) getMethod.invoke(entity));
            } else if (getMethod.getReturnType().getSimpleName().indexOf("Date") != -1) {
                statement.setDate(i++, (Date) getMethod.invoke(entity));
            } else if (getMethod.getReturnType().getSimpleName().indexOf("Double") != -1) {
                statement.setDouble(i++, (Double) getMethod.invoke(entity));
            } else if (getMethod.getReturnType().getSimpleName().indexOf("Timestamp") != -1) {
                statement.setTimestamp(i++, (Timestamp) getMethod.invoke(entity));
            } else if (getMethod.getReturnType().getSimpleName().indexOf("Long") != -1) {
                statement.setLong(i++, (Long) getMethod.invoke(entity));
            } else {
                statement.setInt(i++, (Integer) getMethod.invoke(entity));
            }
        }
    }
}

